package com.atguigu.security.filter;

import com.atguigu.security.entity.SecurityUser;
import com.atguigu.security.entity.User;
import com.atguigu.security.security.TokenManager;
import com.atguigu.servicebase.utils.R;
import com.atguigu.servicebase.utils.ResponseUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

//自定义认证过滤器,UsernamePasswordAuthenticationFilter是SpringSecurity提供的类
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    //Token工具类,用于生成token或根据token获取具体内容
    private TokenManager tokenManager;
    //Redis操作工具类
    private RedisTemplate redisTemplate;
    //权限管理工具,由SpringSecurity提供
    private AuthenticationManager authenticationManager;

    public TokenLoginFilter(TokenManager tokenManager,RedisTemplate redisTemplate,AuthenticationManager authenticationManager){
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.authenticationManager = authenticationManager;
        //仅支持Post提交,改为false
        this.setPostOnly(false);
        //设置登录的路径,让登录路径匹配一个POST提交
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }

    //获取表单提交的用户名和密码
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //获取表单提交的数据
        try {
            //得到前端传过来的表单的数据
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
            //三个参数为 用户名、密码和权限, 在这个过程中,会调用UserDetailService的实现类去查询数据库
            return this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("登录异常!");
        }
    }

    //认证成功会调用的方法: 形参中有一个Authentication,该参数即为上面的方法认证成功之后返回的内容
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        //调用getPrincipal()后可以得到认证之后的用户信息
        SecurityUser user = (SecurityUser)authResult.getPrincipal();
        //认证成功之后,进行两件事,第一件事根据用户名生成token,第二件事为将用户名称和用户权限列表放到redis中
        //调用Token工具类,根据用户名生成token
        String token = this.tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        //将用户名称和用户权限列表放到redis中(key为用户名称,value为权限列表)
        this.redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
        //将token的值进行返回(会将token放到请求头中)
        ResponseUtil.out(response, R.ok().data("token",token));
    }


    //认证失败会调用的方法,直接返回一个错误的提示即可
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
